
public class GPSLocation {

    private String rawData;
    private int time, numberOfSatellites, fixQuality;
    private double latitude, longitude, horizontalAccuracy, altitude, geoidalSeparation;
    private boolean latNorth, longEast;//holds whether latitude is north and longitude is east
    private static final double METERS_PER_LATITUDE_DEGREE = 111141.515805, METERS_PER_LONGITUDE_DEGREE = 78158.0345;
    private final double EARTH_RADIUS =  6371.009;  // earth's mean radius in km


    public GPSLocation(String s) {
        buildData(s);
    }    
    public GPSLocation(double lat, double lon){
    	this.setLatitude(lat);
    	this.setLongitude(lon);
    }

    private int intValueOf(String s) {

        int ret = 0;
        char c[] = s.toCharArray();

        for (int i = 0; i < c.length; i++) {

            if ((int) c[i] < 48 || (int) c[i] > 57) {

                break;

            }

            ret = ret * 10 + ((int) c[i] - 48);

        }

//        System.out.println("" + ret);

        return ret;
    }

    private double floatValueOf(String s) {
        int i = 0;
        double ret = 0;

        char c[] = s.toCharArray();

        for (; i < c.length; i++) {

            if ((int) c[i] < 48 || (int) c[i] > 57) {
                i++;
                break;
            }

            ret = ret * 10 + ((int) c[i] - 48);
        }

        double dec = 1;

        for (; i < c.length; i++) {

            if ((int) c[i] < 48 || (int) c[i] > 57) {
                break;
            }

            dec = (double) (dec * 0.1);
            ret = ret + (double) ((int) c[i] - 48) * dec;

        }

//        System.out.println("" + ret);

        return ret;

    }



    public void buildData(String s) {

        char c[] = s.toCharArray();
        char data[][] = new char[100][s.length()];  // 100 elements to give contingency for bad gps data
        int i = 0, j = 0;

        for (int h = 0; h < c.length; h++) {

            if (c[h] == ',') {
                i++;
                j = 0;

            } else {

                data[i][j] = c[h];
                j++;

            }
        }

        time = intValueOf(new String(data[1]));
        
        //if it is north, else neg for south
        if(data[3][0] == 'N'){
            latitude = floatValueOf(new String(data[2])) / 100;
        } else {
            latitude = (-1 * floatValueOf(new String(data[2]))) / 100;        	
        }
        latNorth = data[3][0] == 'N';

        //if East, otherwise neg for West
        if(data[5][0] == 'E'){
            longitude = floatValueOf(new String(data[4])) / 100;        	
        } else {
            longitude = (-1 * floatValueOf(new String(data[4]))) / 100;        	
        }
        longEast = data[5][0] == 'E';
        fixQuality = intValueOf(new String(data[6]));
        numberOfSatellites = intValueOf(new String(data[7]));
        horizontalAccuracy = floatValueOf(new String(data[8]));
        altitude = floatValueOf(new String(data[9]));
        geoidalSeparation = intValueOf(new String(data[11]));
    }

    public double getLattitudeDisplacement(GPSLocation other) {
        return (double) ((this.latitude - other.latitude) * METERS_PER_LATITUDE_DEGREE);
    }

    public double getLongitudeDisplacement(GPSLocation other){
        return (double) ((this.longitude - other.longitude) * METERS_PER_LONGITUDE_DEGREE);
    }
    
    public double getAltitude() {
        return altitude;
    }

    public void setAltitude(double altitude){ 
        this.altitude = altitude;
    }


    public int getFixQuality() {
        return fixQuality;
    }

    public void setFixQuality(int fixQuality) {
        this.fixQuality = fixQuality;
    }

    public double getGeoidalSeparation() {
        return geoidalSeparation;
    }

    public void setGeoidalSeparation(double geoidalSeparation) {
        this.geoidalSeparation = geoidalSeparation;
    }

    public double getHorizontalAccuracy() {
        return horizontalAccuracy;
    }

    public void setHorizontalAccuracy(double horizontalAccuracy) {
        this.horizontalAccuracy = horizontalAccuracy;
    }

    public boolean isLatNorth() {
        return latNorth;
    }

    public void setLatNorth(boolean latNorth) {
        this.latNorth = latNorth;
    }

    public double getLatitude() {
        return latitude;
    }

    public void setLatitude(double latitude) {
        this.latitude = latitude;
    }

    public boolean isLongEast() {
       return longEast;
    }

    public void setLongEast(boolean longEast) {
        this.longEast = longEast;
    }

    public double getLongitude() {
        return longitude;
   }

    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }

    public int getNumberOfSatellites() {
        return numberOfSatellites;
    }



    public void setNumberOfSatellites(int numberOfSatellites) {
        this.numberOfSatellites = numberOfSatellites;
    }



    public String getRawData() {
        return rawData;
    }



    public void setRawData(String rawData) {
        this.rawData = rawData;
    }


    public int getTime() {
        return time;
    }
    
    public double getLatCoord(){
    	double rawLat = this.getLatitude();
    	int	degLat = (int)Math.floor(rawLat);
    	double minLat = (rawLat - degLat) * 100;
    	minLat = minLat - Math.floor(minLat);   	
    	double secLat = Math.floor(minLat * 60);
    	minLat = (rawLat - degLat) * 100;
    	minLat = Math.floor(minLat);
    	double latCoord = degLat + (minLat / 60) + (secLat / 3600);
    	
    	return latCoord;
    }
    
    public double getLngCoord(){
    	int degLng;
    	double rawLng = this.getLongitude();   	
    	if(rawLng >= 0)
    		degLng = (int)Math.floor(rawLng);
    	else
    		degLng = (int)Math.ceil(rawLng);    	
   	    	
    	double minLng = (rawLng - degLng) * 100;    	    	
    	minLng = minLng - Math.floor(minLng);
    	double secLng = Math.floor(minLng * 60);
    	minLng = (rawLng - degLng) * 100;
    	minLng = Math.floor(minLng);
    	double lngCoord = degLng + (minLng / 60) + (secLng / 3600);
    	
    	return lngCoord;
    }

    public int getDegLat() {
    	double rawLat = this.getLatitude();
    	int	degLat = (int)Math.floor(rawLat);
    	double minLat = (rawLat - degLat) * 100;
    	minLat = Math.floor(minLat);
    	
    	return degLat;
    }
    
    public int getDegLng() {
    	int degLng;
    	double rawLng = this.getLongitude();
    	
    	if(rawLng >= 0)
    		degLng = (int)Math.floor(rawLng);
    	else
    		degLng = (int)Math.ceil(rawLng);
    	
    	return degLng;
    }
    
    public double getMinLat() {
    	double rawLat = this.getLatitude();    	
    	int	degLat = (int)Math.floor(rawLat);
    	double minLat = (rawLat - degLat) * 100;
    	minLat = Math.floor(minLat);
    	
    	return minLat;
    }
    
    public double getMinLng() {
    	double rawLng = this.getLongitude();   	
    	int degLng;
    	
    	if(rawLng >= 0)
    		degLng = (int)Math.floor(rawLng);
    	else
    		degLng = (int)Math.ceil(rawLng);
    	
    	double minLng = (rawLng - degLng) * -100;
    	minLng = Math.floor(minLng);
    	
    	return minLng;
    }
    
    public double getSecLat() {
    	double rawLat = this.getLatitude();    	
    	int	degLat = (int)Math.floor(rawLat);
    	double minLat = (rawLat - degLat) * 100;
    	minLat = minLat - Math.floor(minLat);   	
    	double secLat = Math.floor(minLat * 60);
   	
    	return secLat;
    }
    
    public double getSecLng() {
    	double rawLng = this.getLongitude();
    	int degLng;
    	
    	if(rawLng >= 0)
    		degLng = (int)Math.floor(rawLng);
    	else
    		degLng = (int)Math.ceil(rawLng);
    	
    	double minLng = (rawLng - degLng) * -100;
    	minLng = minLng - Math.floor(minLng);
    	double secLng = Math.floor(minLng * 60);
    	
    	return secLng;
    }

    public void setTime(int time) {
        this.time = time;
    }
    
    
    /**
     * Returns the distance from this point to the supplied point, in km 
     * (using Haversine formula)
     *
     * from: Haversine formula - R. W. Sinnott, "Virtues of the Haversine",
     *       Sky and Telescope, vol 68, no 2, 1984
     *
     * @param   {LatLon} point: Latitude/longitude of destination point
     * @param   {Number} [precision=4]: no of significant digits to use for returned value
     * @returns {Number} Distance in km between this point and destination point
     */
    public double distanceTo(GPSLocation g) {
    	
    	//latitude of destination
    	double lat2 = Math.toRadians(g.getLatCoord()), lon2 = Math.toRadians(g.getLngCoord());
    	
    	//System.out.println("lat2 is: "+lat2+"lon2 is: "+lon2);
    	
    	double R = this.EARTH_RADIUS;
    	
    	//current
    	double lat1 = Math.toRadians(this.getLatCoord()), lon1 = Math.toRadians(this.getLngCoord());
    	
    	//System.out.println("lat1 is: "+lat1+"lon1 is: "+lon1);

    	
    	double dLat = lat2 - lat1;
    	double dLon = lon2 - lon1;

    	double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
              Math.cos(lat1) * Math.cos(lat2) * 
              Math.sin(dLon/2) * Math.sin(dLon/2);
      double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
      double d = R * c;
      
      return d * 1000;
    }


    public int bearingTo(GPSLocation g) {
    	int finalBrng;
    	double subBrng;
		double lat1 = Math.toRadians(this.getLatCoord()), lat2 = Math.toRadians(g.getLatCoord());
		double dLon = Math.toRadians((g.getLngCoord()-this.getLngCoord()));
		double y = Math.sin(dLon) * Math.cos(lat2);
		double x = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
		double brng = Math.atan2(y, x);
		subBrng = (Math.toDegrees(brng));
		subBrng = (subBrng+360);
		finalBrng = ((int)(subBrng+0.5) % 360);
		return finalBrng;
    }
}

